package nachos.userprog;

import java.util.HashMap;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

/**
 * A kernel that can support multiple user processes.
 */
public class UserKernel extends ThreadedKernel {
    /**
     * Allocate a new user kernel.
     */
    public UserKernel() {
	super();
    }

    /**
     * Initialize this kernel. Creates a synchronized console and sets the
     * processor's exception handler.
     */
    public void initialize(String[] args) {
	super.initialize(args);

	console = new SynchConsole(Machine.console());
	
	Machine.processor().setExceptionHandler(new Runnable() {
		public void run() { exceptionHandler(); }
	    });
	// init physicalPages, set all to free
	numPhysPages = Machine.processor().getNumPhysPages();
	physicalPages = new boolean[numPhysPages];
//	userProcesses = new UserProcess[numPhysPages];
	for (int i = 0 ; i < numPhysPages ; i++) {
		physicalPages[i] = false;
	}
	lock = new Lock();
    }

    /**
     * Test the console device.
     */	
    public void selfTest() { // TODO restore
	super.selfTest();

	System.out.println("Testing the console device. Typed characters");
	System.out.println("will be echoed until q is typed.");

	char c;

	do {
	    c = (char) console.readByte(true);
	    console.writeByte(c);
	}
	while (c != 'q');

	System.out.println("");
    }

    /**
     * Returns the current process.
     *
     * @return	the current process, or <tt>null</tt> if no process is current.
     */
    public static UserProcess currentProcess() {
	if (!(KThread.currentThread() instanceof UThread))
	    return null;
	
	return ((UThread) KThread.currentThread()).process;
    }

    /**
     * The exception handler. This handler is called by the processor whenever
     * a user instruction causes a processor exception.
     *
     * <p>
     * When the exception handler is invoked, interrupts are enabled, and the
     * processor's cause register contains an integer identifying the cause of
     * the exception (see the <tt>exceptionZZZ</tt> constants in the
     * <tt>Processor</tt> class). If the exception involves a bad virtual
     * address (e.g. page fault, TLB miss, read-only, bus error, or address
     * error), the processor's BadVAddr register identifies the virtual address
     * that caused the exception.
     */
    public void exceptionHandler() {
	Lib.assertTrue(KThread.currentThread() instanceof UThread);

	UserProcess process = ((UThread) KThread.currentThread()).process;
	int cause = Machine.processor().readRegister(Processor.regCause);
	process.handleException(cause);
    }

    /**
     * Start running user programs, by creating a process and running a shell
     * program in it. The name of the shell program it must run is returned by
     * <tt>Machine.getShellProgramName()</tt>.
     *
     * @see	nachos.machine.Machine#getShellProgramName
     */
    public void run() {
	super.run();

	UserProcess process = UserProcess.newUserProcess();
	
	String shellProgram = Machine.getShellProgramName();	
	Lib.assertTrue(process.execute(shellProgram, new String[] { }));

	KThread.currentThread().finish();
    }

    /**
     * Terminate this kernel. Never returns.
     */
    public void terminate() {
	super.terminate();
    }

    /** Globally accessible reference to the synchronized console. */
    public static SynchConsole console;

    // dummy variables to make javac smarter
    private static Coff dummy1 = null;
    
    /**
     * bitmap for physical pages. false stands for free, true stands for used
     */
    private static boolean[] physicalPages;
//    private static UserProcess[] userProcesses;
    protected static int numPhysPages;
    private static Lock lock;
    
    /**
     * allocate one physical page at a time
     * @param userProcess for debug use
     * @return if there is free, return the page number(start from 0)
     * 			if there is no free, return -1
     */
    public static int allocatePhysPage(UserProcess userProcess) {
    	lock.acquire();
    	for (int i = 0;i<numPhysPages;i++) {
    		if (!physicalPages[i]) {
    			physicalPages[i] = true;
//    			userProcesses[i] = userProcess;
    			lock.release();
//    			Lib.debug('g', "\t---phys page: " +i+" allocated for process: "+userProcess.name
//    					+ " pid: "+userProcess.pid);
    			return i;
    		}
    	}
    	lock.release();
    	return -1;
    }
    
    /**
     * 
     * @param pageNumber physical page number to deallocate
     * @param userProcess for debug use
     */
    public static void deallocatePhysPage(int pageNumber, UserProcess userProcess) {
    	lock.acquire();
    	physicalPages[pageNumber] = false;
//    	userProcesses[pageNumber] = null;
    	lock.release();
//    	Lib.debug('g', "\t---phys page: " +pageNumber+" deallocated for process:" + userProcess.name
//    			+ "pid: " + userProcess.pid);
    }
    
//    public static void printPhysPageStatus() {
//    	lock.acquire();
//    	Lib.debug('l', "-------------------------------------------------");
//    	Lib.debug('l', "|PhyPageNum\t|pid\t|pname\t\t\t|");
//    	for(int i = 0 ; i < numPhysPages ; i++) {
//    		if(physicalPages[i] == false) {
//    			Lib.debug('l', "|"+i+"\t\t|\t|\t\t\t|");
//    		} else {
//    			Lib.debug('l', "|"+i+"\t\t|"+userProcesses[i].pid+"\t|"+userProcesses[i].name+"\t|");
//    		}
//    	}
//    	Lib.debug('l', "-------------------------------------------------");
//    	lock.release();
//    }
    
    
}
class PIDManager {
	private static Lock lock = new Lock();
	private static HashMap<Integer, UserProcess> allProcesses = new HashMap<Integer, UserProcess>();
	private static int currentPID = 1;
	
	public static int allocatePID(UserProcess userProcess) {
    	lock.acquire();
    	int pid = currentPID;
    	currentPID++;
    	allProcesses.put(pid, userProcess);
    	lock.release();
    	Lib.debug('g', "pid: "+pid+" gives to"+userProcess.name);
    	return pid;
    }
	
	/**
	 * 
	 * @param pid
	 * @return if no such pid return null
	 */
	public static UserProcess getUserProcess(int pid) {
		lock.acquire();
		if (!allProcesses.containsKey(pid)) {
			lock.release();
			return null;
		}
		UserProcess userProcess = allProcesses.get(pid);
		lock.release();
		return userProcess;
	}
	
	public static boolean hasProcessRunning() {
		lock.acquire();
		for (Integer pid : allProcesses.keySet()) {
			if (allProcesses.get(pid).isRunning) {
				// if any one is running
				lock.release();
				return true;
			}
		}
		lock.release();
		return false; // no one running
	}
}
